package com.docapis.core.parser;

import com.docapis.core.DocContext;
import com.docapis.core.LogUtils;
import com.docapis.core.ParseUtils;
import com.docapis.core.Utils;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.MethodCallExpr;
import com.github.javaparser.ast.expr.ObjectCreationExpr;
import com.github.javaparser.ast.stmt.ExpressionStmt;

import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.List;

/**
 * jFinal routes parser
 * <p>
 * licence Apache 2.0, from japidoc
 **/
public class JFinalRoutesParser {

    private File jfinalConfigFile;
    private MethodDeclaration mdConfigRoute;
    private List<RouteNode> routeNodeList = new ArrayList<>();

    public static final JFinalRoutesParser INSTANCE = new JFinalRoutesParser();

    private JFinalRoutesParser() {
        List<File> result = new ArrayList<>();

        for (String javaSrcPath : DocContext.getJavaSrcPaths()) {
            Utils.wideSearchFile(new File(javaSrcPath), new FilenameFilter() {
                @Override
                public boolean accept(File f, String name) {
                    return ParseUtils.compilationUnit(f).getChildNodesByType(ClassOrInterfaceDeclaration.class)
                            .stream()
                            .anyMatch(cl -> cl.getMethodsByName("configRoute")
                                    .stream()
                                    .anyMatch(m -> {
                                        mdConfigRoute = m;
                                        return m.getParameters()
                                                .stream()
                                                .anyMatch(p -> p.getType().asString().endsWith("Routes"));
                                    }));
                }
            }, result, true);

            if (!result.isEmpty()) {
                break;
            }
        }


        if (result.isEmpty()) {
            throw new RuntimeException("cannot find JFinalConfig File");
        }

        jfinalConfigFile = result.get(0);

        LogUtils.info("Jfinal config file path : %s", jfinalConfigFile.getAbsolutePath());

        parse(mdConfigRoute, jfinalConfigFile);
    }

    private void parse(MethodDeclaration mdConfigRoute, File inJavaFile) {
        mdConfigRoute.getBody()
                .ifPresent(blockStmt -> blockStmt.getStatements()
                        .stream()
                        .filter(statement -> statement instanceof ExpressionStmt)
                        .forEach(statement -> {
                            Expression expression = ((ExpressionStmt) statement).getExpression();
                            if (expression instanceof MethodCallExpr && ((MethodCallExpr) expression).getNameAsString().equals("add")) {
                                NodeList<Expression> arguments = ((MethodCallExpr) expression).getArguments();
                                if (arguments.size() == 1 && arguments.get(0) instanceof ObjectCreationExpr) {
                                    String routeClassName = ((ObjectCreationExpr) ((MethodCallExpr) expression).getArguments().get(0)).getType().getNameAsString();
                                    File childRouteFile = ParseUtils.searchJavaFile(inJavaFile, routeClassName);
                                    LogUtils.info("found child routes in file : %s", childRouteFile.getName());
                                    ParseUtils.compilationUnit(childRouteFile)
                                            .getChildNodesByType(ClassOrInterfaceDeclaration.class)
                                            .stream()
                                            .filter(cd -> routeClassName.endsWith(cd.getNameAsString()))
                                            .findFirst()
                                            .ifPresent(cd -> {
                                                LogUtils.info("found config() method, start to parse child routes in file : %s", childRouteFile.getName());
                                                cd.getMethodsByName("config").stream().findFirst().ifPresent(m -> {
                                                    parse(m, childRouteFile);
                                                });
                                            });
                                } else {
                                    String basicUrl = Utils.removeQuotations(arguments.get(0).toString());
                                    String controllerClass = arguments.get(1).toString();
                                    String controllerFilePath = getControllerFilePath(inJavaFile, controllerClass);
                                    routeNodeList.add(new RouteNode(basicUrl, controllerFilePath));
                                }
                            }
                        }));
    }

    public List<RouteNode> getRouteNodeList() {
        return routeNodeList;
    }

    public RouteNode getRouteNode(String controllerFile) {
        for (RouteNode routeNode : routeNodeList) {
            if (routeNode.controllerFile.equals(controllerFile)) {
                return routeNode;
            }
        }
        return null;
    }

    private String getControllerFilePath(File inJavaFile, String controllerClass) {
        return ParseUtils.searchJavaFile(inJavaFile, controllerClass.substring(0, controllerClass.indexOf("."))).getAbsolutePath();
    }

    public static class RouteNode {

        public final String basicUrl;
        public final String controllerFile;

        public RouteNode(String basicUrl, String controllerFile) {
            this.basicUrl = basicUrl;
            this.controllerFile = controllerFile;
        }
    }

}
